/*
 * Alloy Analyzer
 * Copyright (c) 2004 Massachusetts Institute of Technology
 *
 * The Alloy Analyzer is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * The Alloy Analyzer is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with the Alloy Analyzer; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

package alloy.gui;

import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.EventListener;
import java.util.EventObject;

import javax.swing.AbstractAction;
import javax.swing.DefaultListModel;
import javax.swing.JList;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JSplitPane;
import javax.swing.KeyStroke;
import javax.swing.ListSelectionModel;
import javax.swing.border.EmptyBorder;

import alloy.util.ParameterEvent;
import alloy.util.ParameterListener;
import alloy.util.Params;

/** ModelPanel is a panel which holds the text of a model.
 *  A LocatableTextArea is what actually displays the text and maintains other
 *  state information about the file.
 */

class ModelPanel extends JPanel {
    private static int newFileCount = 1; // count for labelling untitled models

    private final LocatableTextArea _textArea;
    private JScrollPane _textScrollPane;
    private GUIMsgListener _msgListener;
    private AlloySplitPane _splitPane;

    private JScrollPane _msgPane;

    private TextTearOffWindow tearOffWindow;

    private java.util.List listenerList; // of registered ModelPanelListeners

    private ParameterListener fontUpdater;
    private ParameterListener tabUpdater;

    ModelPanel(File file, final boolean editable) throws IOException {
	super(new BorderLayout());

	_textArea = new LocatableTextArea();
	_textArea.setEditable(editable);

	_textArea.setFont(Params.glob.getFontParam("GUI", "textfont"));
	_textArea.setTabSize(Params.glob.getIntParam("GUI", "tabwidth"));

	if (file == null) {
	    _textArea.setUntitledName("<Untitled " + (newFileCount++) + ">");
	} else {
	    if (!_textArea.readFile(file.getPath(), false))
		throw new IOException();
	}

	_textScrollPane = new JScrollPane(_textArea);

	listenerList = new ArrayList();

	// Code that supports display of cursor position, currently disabled.
	// To enable the line numbers, you also need to uncomment the line
	// that adds southBox to the contentPane.
	/*final JLabel caretPositionLabel = new JLabel ();
	  Box southBox = Box.createHorizontalBox();
	  southBox.add(Box.createHorizontalGlue());
	  southBox.add (caretPositionLabel);
	  southBox.add(Box.createHorizontalStrut (12));

	  CaretListener caretListener = (CaretListener)AlloySwingUtilities.listener(new CaretListener() {
	  public void caretUpdate (CaretEvent e) {
	  int row = 1, col = 1;

	  if (e != null) {
	  int pos = e.getDot();
	  try {
	  row = _textArea.getLineOfOffset (pos);
	  col = pos - _textArea.getLineStartOffset (row);

	  // Translate from zero-based indexing to one-based indexing.
	  row++;
	  col++;
	  }
	  catch (BadLocationException ex) {
	  }
	  }

	  caretPositionLabel.setText ("Line " + row + ", Column " + col);
	  }
	  });
	  _textArea.addCaretListener(caretListener);
	  caretListener.caretUpdate(null);*/

	// when to remove these...?
	fontUpdater = new ParameterListener() {
		public void valueChanged(ParameterEvent e) {
		    _textArea.setFont(Params.glob.getFontParam("GUI", "textfont"));
		}
	    };
	Params.glob.addParameterListener("GUI","textfont",fontUpdater);
	tabUpdater = new ParameterListener() {
		public void valueChanged(ParameterEvent e) {
		    _textArea.setTabSize(Params.glob.getIntParam("GUI", "tabwidth"));
		}
	    };
	Params.glob.addParameterListener("GUI","tabwidth",tabUpdater);
	
	JList _compilerMsgList = new WrappingList(new DefaultListModel());
	_compilerMsgList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);

	_msgListener = new GUIMsgListener(this, _compilerMsgList);
	_msgPane = new JScrollPane(_compilerMsgList);

	_splitPane = new AlloySplitPane(JSplitPane.VERTICAL_SPLIT);
	_splitPane.setContinuousLayout(false);
	_splitPane.setTopComponent(_textScrollPane);
	_splitPane.setBottomComponent(_msgPane); //new JScrollPane(_compilerMsgList));
	_splitPane.setOneTouchExpandable(true);
	_splitPane.setResizeWeight(1.0);
	if (AlloySwingUtilities.onMac()) {
	    _splitPane.setBorder(null);
	    _textScrollPane.setBorder(null);
	    _textArea.setBorder(new EmptyBorder(5, 5, 0, 0));
	}
        /*
	  setDefaultCloseOperation(DO_NOTHING_ON_CLOSE);
	  addWindowListener((WindowListener)AlloySwingUtilities.listener(new WindowAdapter() {
	  public void windowClosing(WindowEvent e) {
	  closeWindow();
	  }
	  }));
        */
	add(_splitPane, BorderLayout.CENTER);
	//getContentPane().add(southBox, BorderLayout.SOUTH);

	hideMsgPane();

	// NOTE: call {show/hide}MsgPane() when window is visible for better results...

	_textArea.addTextChangedListener(new LocatableTextArea.TextChangedListener() {
                public void textChanged(LocatableTextArea.TextChangedEvent event) {
		    fireModelChanged();
                    _textArea.requestFocus();
                }
            });

	_textArea.addFileSavedListener(new LocatableTextArea.FileSavedListener() {
                public void fileSaved(LocatableTextArea.FileSavedEvent event) {
		    fireModelChanged();
                }
            });

	_textArea.addFileChangedListener(new LocatableTextArea.FileChangedListener() {
                public void fileChanged(LocatableTextArea.FileChangedEvent event) {
		    fireModelChanged();
                }
            });
    }

    void openTearOff() {
	if (tearOffWindow == null && _textArea.getFilePath() != null) {
	    tearOffWindow = new TextTearOffWindow(getFile());
	    tearOffWindow.setSize(500, 500);
	}
	if (tearOffWindow != null) {
	    tearOffWindow.setVisible(true);
	    tearOffWindow.requestFocus();
	}
    }

    LocatableTextArea getTextArea() {
	return _textArea;
    }

    String getFileString() {
	return _textArea.getFileString();
    }

    File getFile() {
	return _textArea.getFilePath() == null ? null : new File(_textArea.getFilePath());
    }

    GUIMsgListener getGUIMsgListener() {
	return _msgListener;
    }

    boolean isEditable() {
	return _textArea.isEditable();
    }

    void showMsgPane() {
	_splitPane.setDividerVisible(true);
	_splitPane.setBottomComponent(_msgPane);
	_splitPane.setDividerLocation(0.7);
        _splitPane.resetToPreferredSizes();
    }

    void hideMsgPane() {
	_splitPane.setDividerLocation(1.0);
	_splitPane.setBottomComponent(null);
	_splitPane.setDividerVisible(false);
    }

    void close() {
	if (tearOffWindow != null)
	    tearOffWindow.close();
	    
	_msgListener.dbgDeregister();
    }

    boolean confirm() {
	return _textArea.checkConfirmOperation();
    }

    void addModelPanelListener(ModelPanelListener l) {
	listenerList.add(l);
    }

    void removeModelPanelListener(ModelPanelListener l) {
	listenerList.remove(l);
    }

    protected void fireModelChanged() {
	ModelPanelListener[] listeners = 
	    (ModelPanelListener[])listenerList.toArray(new ModelPanelListener[0]);
	ModelPanelEvent event = new ModelPanelEvent(this);
	for (int i=listeners.length-1; i>=0; i--) {
	    listeners[i].modelChanged(event);
	}
    }

    protected void fireModelError(Object message) {
	ModelPanelListener[] listeners = 
	    (ModelPanelListener[])listenerList.toArray(new ModelPanelListener[0]);
	ModelPanelEvent event = new ModelPanelEvent(this, message);
	for (int i=listeners.length-1; i>=0; i--) {
	    listeners[i].modelError(event);
	}
    }
}

interface ModelPanelListener extends EventListener {
    public void modelChanged(ModelPanelEvent e);
    public void modelError(ModelPanelEvent e);
}

class ModelPanelEvent extends EventObject {
    private Object _message;
    
    public ModelPanelEvent(ModelPanel source) {
	this(source, null);
    }

    public ModelPanelEvent(ModelPanel source, Object message) {
	super(source);
	_message = message;
    }

    public ModelPanel getModelPanel() {
	return (ModelPanel)getSource();
    }

    public Object getMessage() {
	return _message;
    }
}

class TextTearOffWindow extends AlloyWindow {
    private final LocatableTextArea _textArea;
    private JScrollPane _textScrollPane;
    private String _name;

    private ParameterListener fontUpdater;
    private ParameterListener tabUpdater;

    TextTearOffWindow(File file) {
	super();
	_textArea = new LocatableTextArea();
	_textArea.setEditable(false);
	_name = file.getName();
	_textArea.readFile(file.getPath(), false);

	setTitle(_name + " (Read-Only)");

	_textArea.setFont(Params.glob.getFontParam("GUI", "textfont"));
	_textArea.setTabSize(Params.glob.getIntParam("GUI", "tabwidth"));

	_textScrollPane = new JScrollPane(_textArea);

	// when to remove these ...?
	fontUpdater = new ParameterListener() {
		public void valueChanged(ParameterEvent e) {
		    _textArea.setFont(Params.glob.getFontParam("GUI", "textfont"));
		}
	    };
	Params.glob.addParameterListener("GUI","textfont",fontUpdater);
	tabUpdater = new ParameterListener() {
		public void valueChanged(ParameterEvent e) {
		    _textArea.setTabSize(Params.glob.getIntParam("GUI", "tabwidth"));
		}
	    };
	Params.glob.addParameterListener("GUI","tabwidth",tabUpdater);
	
	_textArea.setBorder(new EmptyBorder(5, 5, 0, 0));
	if (AlloySwingUtilities.onMac()) {
	    _textScrollPane.setBorder(null);
	}

	getContentPane().add(_textScrollPane, BorderLayout.CENTER);
	JMenuBar mb = createMenuBar();
	setJMenuBar(mb);
	pack();
    }

    void close() {
	// work-around for possible memory leak in Mac OS X Java 1.4.1
	remove(this.getJMenuBar());

	dispose();
    }

    JMenuBar createMenuBar() {
	JMenuBar mb = new JMenuBar();
	int shortcutKeyMask = AlloySwingUtilities.getShortcutKeyMask();

	// The file menu.
	JMenu fileMenu = new JMenu("File");
	fileMenu.setMnemonic('F');
	mb.add(fileMenu);

	// And now its contents.
	JMenuItem closeMenuItem = new JMenuItem("Close Window", 'o');
	closeMenuItem.setAction(AlloySwingUtilities.action(new AbstractAction("Close Window") {
                public void actionPerformed(ActionEvent e) {
                    setVisible(false);
                }
            }));
	closeMenuItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_W, shortcutKeyMask));
	fileMenu.add(closeMenuItem);

	// The edit menu.
	JMenu editMenu = new JMenu("Edit");
	editMenu.setMnemonic('E');
	mb.add(editMenu);

	JMenuItem copyMenuItem = new JMenuItem("Copy", 'C');
	copyMenuItem.setAction(AlloySwingUtilities.action(new AbstractAction("Copy") {
                public void actionPerformed(ActionEvent e) {
                    _textArea.copy();
                }
            }));
	copyMenuItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_C, shortcutKeyMask));
	editMenu.add(copyMenuItem);

	JMenu windowMenu = AlloyWindow.makeWindowMenu("Window");
	windowMenu.setMnemonic('W');
	mb.add(windowMenu);

	return mb;
    }
}
